//===--- TypeLayoutVerifier.cpp -------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines a generator that produces code to verify that IRGen's
// static assumptions about data layout for a Swift type correspond to the
// runtime's understanding of data layout.
//
//===----------------------------------------------------------------------===//

#include "llvm/IR/Function.h"
#include "llvm/IR/Module.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/Types.h"
#include "EnumPayload.h"
#include "IRGenFunction.h"
#include "IRGenModule.h"
#include "GenOpaque.h"
#include "GenType.h"
#include "FixedTypeInfo.h"

using namespace swift;
using namespace irgen;

void
irgen::emitTypeLayoutVerifier(IRGenFunction &IGF,
                              ArrayRef<CanType> formalTypes) {
  auto verifierFn = IGF.IGM.getVerifyTypeLayoutAttributeFn();

  struct VerifierArgumentBuffers {
    Address runtimeBuf, staticBuf;
  };
  llvm::DenseMap<llvm::Type *, VerifierArgumentBuffers>
    verifierArgBufs;
  
  auto getSizeConstant = [&](Size sz) -> llvm::Constant * {
    return llvm::ConstantInt::get(IGF.IGM.SizeTy, sz.getValue());
  };
  auto getAlignmentMaskConstant = [&](Alignment a) -> llvm::Constant * {
    return llvm::ConstantInt::get(IGF.IGM.SizeTy, a.getValue() - 1);
  };
  auto getBoolConstant = [&](bool b) -> llvm::Constant * {
    return llvm::ConstantInt::get(IGF.IGM.Int1Ty, b);
  };

  SmallString<20> numberBuf;

  for (auto formalType : formalTypes) {
    // Runtime type metadata always represents the maximal abstraction level of
    // the type.
    auto maxAbstraction = AbstractionPattern::getOpaque();
    auto &ti = IGF.getTypeInfoForUnlowered(maxAbstraction, formalType);
    
    // Check type metrics for fixed-layout types.
    // If there's no fixed type info, we rely on the runtime for type metrics,
    // so there's no compile-time values to validate against.
    if (auto *fixedTI = dyn_cast<FixedTypeInfo>(&ti)) {
      auto metadata = IGF.emitTypeMetadataRef(formalType);
    
      auto verifyValues = [&](llvm::Value *runtimeVal,
                        llvm::Value *staticVal,
                        const llvm::Twine &description) {
        assert(runtimeVal->getType() == staticVal->getType());
        // Get or create buffers for the arguments.
        VerifierArgumentBuffers bufs;
        auto foundBufs = verifierArgBufs.find(runtimeVal->getType());
        if (foundBufs != verifierArgBufs.end()) {
          bufs = foundBufs->second;
        } else {
          Address runtimeBuf = IGF.createAlloca(runtimeVal->getType(),
                                                IGF.IGM.getPointerAlignment(),
                                                "runtime");
          Address staticBuf = IGF.createAlloca(staticVal->getType(),
                                               IGF.IGM.getPointerAlignment(),
                                               "static");
          bufs = {runtimeBuf, staticBuf};
          verifierArgBufs[runtimeVal->getType()] = bufs;
        }
        
        IGF.Builder.CreateStore(runtimeVal, bufs.runtimeBuf);
        IGF.Builder.CreateStore(staticVal, bufs.staticBuf);
        
        auto runtimePtr = IGF.Builder.CreateBitCast(bufs.runtimeBuf.getAddress(),
                                                    IGF.IGM.Int8PtrTy);
        auto staticPtr = IGF.Builder.CreateBitCast(bufs.staticBuf.getAddress(),
                                                   IGF.IGM.Int8PtrTy);
        auto count = llvm::ConstantInt::get(IGF.IGM.SizeTy,
                      IGF.IGM.DataLayout.getTypeStoreSize(runtimeVal->getType()));
        auto msg
          = IGF.IGM.getAddrOfGlobalString(description.str());
        
        IGF.Builder.CreateCall(
            verifierFn, {metadata, runtimePtr, staticPtr, count, msg});
      };

      auto verifyBuffers = [&](Address runtimeBuf,
                               Address staticBuf,
                               Size size,
                               const llvm::Twine &description) {
        auto runtimePtr = IGF.Builder.CreateBitCast(runtimeBuf.getAddress(),
                                                    IGF.IGM.Int8PtrTy);
        auto staticPtr = IGF.Builder.CreateBitCast(staticBuf.getAddress(),
                                                   IGF.IGM.Int8PtrTy);
        auto count = llvm::ConstantInt::get(IGF.IGM.SizeTy,
                                            size.getValue());
        auto msg
          = IGF.IGM.getAddrOfGlobalString(description.str());
  
        IGF.Builder.CreateCall(
            verifierFn, {metadata, runtimePtr, staticPtr, count, msg});
      };
      // Check that the fixed layout matches the runtime layout.
      SILType layoutType = SILType::getPrimitiveObjectType(formalType);
      verifyValues(emitLoadOfSize(IGF, layoutType),
             getSizeConstant(fixedTI->getFixedSize()),
             "size");
      verifyValues(emitLoadOfAlignmentMask(IGF, layoutType),
             getAlignmentMaskConstant(fixedTI->getFixedAlignment()),
             "alignment mask");
      verifyValues(emitLoadOfStride(IGF, layoutType),
             getSizeConstant(fixedTI->getFixedStride()),
             "stride");
      verifyValues(emitLoadOfIsInline(IGF, layoutType),
             getBoolConstant(fixedTI->getFixedPacking(IGF.IGM)
                               == FixedPacking::OffsetZero),
             "is-inline bit");
      verifyValues(emitLoadOfIsPOD(IGF, layoutType),
             getBoolConstant(fixedTI->isPOD(ResilienceExpansion::Maximal)),
             "is-POD bit");
      verifyValues(emitLoadOfIsBitwiseTakable(IGF, layoutType),
             getBoolConstant(fixedTI->isBitwiseTakable(ResilienceExpansion::Maximal)),
             "is-bitwise-takable bit");
      unsigned xiCount = fixedTI->getFixedExtraInhabitantCount(IGF.IGM);
      verifyValues(emitLoadOfHasExtraInhabitants(IGF, layoutType),
             getBoolConstant(xiCount != 0),
             "has-extra-inhabitants bit");

      // Check extra inhabitants.
      if (xiCount > 0) {
        verifyValues(emitLoadOfExtraInhabitantCount(IGF, layoutType),
               getSizeConstant(Size(xiCount)),
               "extra inhabitant count");
        
        // Verify that the extra inhabitant representations are consistent.
        
        // TODO: Update for EnumPayload implementation changes.
        auto xiBuf = IGF.createAlloca(fixedTI->getStorageType(),
                                      fixedTI->getFixedAlignment(),
                                      "extra-inhabitant");
        auto fixedXIBuf = IGF.createAlloca(fixedTI->getStorageType(),
                                           fixedTI->getFixedAlignment(),
                                           "extra-inhabitant");
        auto xiOpaque = IGF.Builder.CreateBitCast(xiBuf, IGF.IGM.OpaquePtrTy);
        auto fixedXIOpaque = IGF.Builder.CreateBitCast(fixedXIBuf,
                                                       IGF.IGM.OpaquePtrTy);
        auto xiMask = fixedTI->getFixedExtraInhabitantMask(IGF.IGM);
        auto xiSchema = EnumPayloadSchema::withBitSize(xiMask.getBitWidth());

        // TODO: Randomize the set of extra inhabitants we check.
        unsigned bits = fixedTI->getFixedSize().getValueInBits();
        for (unsigned i = 0, e = std::min(xiCount, 256u);
             i < e; ++i) {
          // Initialize the buffer with junk, to help ensure we're insensitive to
          // insignificant bits.
          // TODO: Randomize the filler.
          IGF.Builder.CreateMemSet(xiBuf.getAddress(),
                                   llvm::ConstantInt::get(IGF.IGM.Int8Ty, 0x5A),
                                   fixedTI->getFixedSize().getValue(),
                                   fixedTI->getFixedAlignment().getValue());
          
          // Ask the runtime to store an extra inhabitant.
          auto index = llvm::ConstantInt::get(IGF.IGM.Int32Ty, i);
          emitStoreExtraInhabitantCall(IGF, layoutType, index, xiOpaque);
          
          // Compare the stored extra inhabitant against the fixed extra
          // inhabitant pattern.
          auto fixedXIValue
             = fixedTI->getFixedExtraInhabitantValue(IGF.IGM, bits, i);
          auto fixedXIPayload =
            EnumPayload::fromBitPattern(IGF.IGM, fixedXIValue,
                                        xiSchema);
          fixedXIPayload.store(IGF, fixedXIBuf);
          
          auto runtimeXIPayload = EnumPayload::load(IGF, xiBuf, xiSchema);
          runtimeXIPayload.emitApplyAndMask(IGF, xiMask);
          runtimeXIPayload.store(IGF, xiBuf);

          numberBuf.clear();
          {
            llvm::raw_svector_ostream os(numberBuf);
            os << i;
          }
          
          verifyBuffers(xiBuf, fixedXIBuf, fixedTI->getFixedSize(),
                 llvm::Twine("stored extra inhabitant ") + numberBuf.str());
          
          // Now ask the runtime to identify the fixed extra inhabitant value.
          // Mask in junk to make sure the runtime correctly ignores it.
          // TODO: Randomize the filler.
          auto xiFill = ~APInt(fixedXIValue.getBitWidth(), 0);
          xiFill &= ~xiMask;
          fixedXIValue |= xiFill;

          auto maskedXIPayload = EnumPayload::fromBitPattern(IGF.IGM,
            fixedXIValue, xiSchema);
          maskedXIPayload.store(IGF, fixedXIBuf);
          
          auto runtimeIndex = emitGetExtraInhabitantIndexCall(IGF, layoutType,
                                                              fixedXIOpaque);
          verifyValues(runtimeIndex, index,
                       llvm::Twine("extra inhabitant index calculation ")
                         + numberBuf.str());
        }
      }
    }

    // TODO: Verify interesting layout properties specific to the kind of type,
    // such as struct or class field offsets, enum case tags, vtable entries,
    // etc.
  }
}
